package com.github.xiaomaoguai.xxr.autoconfigure;


import com.github.xiaomaoguai.xxr.dto.req.BizReq;
import com.github.xiaomaoguai.xxr.dto.req.PublicReqDTO;
import com.github.xiaomaoguai.xxr.dto.resp.PublicRespDTO;
import com.github.xiaomaoguai.xxr.dto.resp.ResultBase;
import com.github.xiaomaoguai.xxr.properties.ZaProperties;
import com.github.xiaomaoguai.xxr.utils.http.HttpClientUtil;
import com.github.xiaomaoguai.xxr.utils.json.JsonUtils;
import com.github.xiaomaoguai.xxr.utils.security.AES;
import com.github.xiaomaoguai.xxr.utils.security.RSA;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author: WeiHui
 * @version: v1.0.0
 * @since JDK 1.8
 */
@Slf4j
public class ZaRemoteInvocationHandler implements InvocationHandler {

	/**
	 * 防止Idea调试报错，加上toString方法
	 */
	private static final String TO_STRING_METHOD = "toString";

	private static final Map<Method, String> methodCache = new ConcurrentHashMap<>();

	/**
	 * 众安提供  aesKey
	 */
	private String aesKey = "BF58217B493783BB";

	private ZaProperties zaProperties;

	public ZaRemoteInvocationHandler(ZaProperties zaProperties) {
		this.zaProperties = zaProperties;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		String methodName = method.getName();
		if (TO_STRING_METHOD.equals(methodName)) {
			return "toString";
		} else {
			ZaPath zaPath = method.getAnnotation(ZaPath.class);
			if (zaPath == null) {
				throw new IllegalArgumentException("请求路径不能为空");
			}
			if (args == null) {
				throw new IllegalArgumentException("请求参数不能为空");
			}
			Object arg = args[0];
			if (!(arg instanceof BizReq)) {
				throw new IllegalArgumentException("请求参数不能为空");
			}
			BizReq bizReq = (BizReq) arg;

			String thirdUserNo = bizReq.getThirdUserNo();
			String token = buildToken(thirdUserNo, this.zaProperties.getChannelId(), aesKey);
			bizReq.setToken(token);
			bizReq.setReqDate(FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss").format(new Date()));

			log.info("请求众安业务参数明文：\n {}", JsonUtils.toFormatJsonString(bizReq));
			PublicReqDTO publicReqDTO = buildPublicReqDTO(bizReq, aesKey);
			String reqUrl = buildReqUrl(this.zaProperties, zaPath);
			log.info("请求URL:\n【 {} 】, 请求众安参数密文：\n {}", reqUrl, JsonUtils.toFormatJsonString(publicReqDTO));
			String param = JsonUtils.toJsonString(publicReqDTO);
			String result = HttpClientUtil.doPost(reqUrl, param);
			String respJson = result.substring(1, result.length() - 1).replace("\\u003d\\", "=").replaceAll("\\\\", "");
			log.info("众安返回报文密文：\n {}", JsonUtils.formatJson(respJson));

			PublicRespDTO respDTO = JsonUtils.toJavaObject(respJson, PublicRespDTO.class);
			String sign = respDTO.getSign();
			String respContent = respDTO.getRespContent();
			//返回报文--第一步： 验证签名
			if (RSA.checkSign(aesKey, sign, this.zaProperties.getPublicKey())) {
				//第二步 ： 解密
				respContent = respContent.replace("u003d", "=");
				String decryptContent = AES.decryptFromBase64(respContent, aesKey);
				log.info("众安返回报文明文：\n {}", JsonUtils.formatJson(decryptContent));
				Type genericType = ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0];
				ResultBase resultBase = JsonUtils.toJavaObject(decryptContent, ResultBase.class);
				Object object = JsonUtils.toJavaObject(decryptContent, Class.forName(genericType.getTypeName()));

				//noinspection unchecked
				resultBase.setValue(object);
				return resultBase;
			} else {
				throw new IllegalArgumentException("签名验证不通过");
			}
		}
	}

	private PublicReqDTO buildPublicReqDTO(BizReq bizReq, String aesKey) throws Exception {
		PublicReqDTO publicReqDTO = new PublicReqDTO();
		String client = bizReq.getClient();
		publicReqDTO.setClient(StringUtils.isBlank(client) ? this.zaProperties.getClient() : client);
		publicReqDTO.setClientIP(bizReq.getClientIP());
		publicReqDTO.setClientMAC(bizReq.getClientMAC());
		publicReqDTO.setChannelID(this.zaProperties.getChannelId());
		String key = RSA.encrypt(aesKey, this.zaProperties.getPublicKey());
		publicReqDTO.setKey(key);
		String reqString = JsonUtils.toJsonString(bizReq);
		String reqContent = AES.encryptToBase64(reqString, aesKey);
		publicReqDTO.setReqContent(reqContent);
		return publicReqDTO;
	}

	private static String buildToken(String thirdUserNo, String channelId, String aesKey) {
		long currentTimeMillis = System.currentTimeMillis() / 1000;
		String token = thirdUserNo + "|" + currentTimeMillis + "|" + channelId;
		return AES.encryptToBase64(token, aesKey);
	}

	private static String buildReqUrl(ZaProperties zaProperties, ZaPath zaPath) {
		return zaProperties.getUrl() + zaPath.value();
	}
}
